package ink.metoo.gude.module.websocket.interceptor

import ink.metoo.gude.module.security.domain.JwtConst
import ink.metoo.gude.module.security.domain.RoleConst
import ink.metoo.gude.module.security.domain.SessionAuthenticationToken
import ink.metoo.gude.module.security.domain.SimpleGrantedAuthority
import ink.metoo.gude.module.security.domain.ddl.Role
import ink.metoo.gude.module.security.service.TokenService
import ink.metoo.gude.module.security.util.original
import ink.metoo.gude.module.websocket.util.asStompHeaderAccessor
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.simp.stomp.StompCommand
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
import org.springframework.messaging.support.ChannelInterceptor
import org.springframework.security.authentication.AnonymousAuthenticationToken
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Component

@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
class JwtAuthenticationChannelInterceptor(
    private val tokenService: TokenService,
    private val userDetailsService: UserDetailsService,
) : ChannelInterceptor {

    override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
        val accessor = message.asStompHeaderAccessor()
        if (accessor.command == StompCommand.CONNECT) {
            accessor.getFirstNativeHeader(JwtConst.AUTHORIZATION_HEADER_NAME).also { header ->
                if (header != null && header.startsWith(JwtConst.BEARER)) {
                    val token = header.substring(JwtConst.BEARER.length)
                    val claims = tokenService.getClaims(token)
                    if (claims == null) {
                        accessor.setAnonymous()
                        return@also
                    }
                    val userDetails = userDetailsService.loadUserByUsername(claims.subject) ?: return message
                    if (!tokenService.isTokenExpired(claims, userDetails.original!!)) {
                        val authentication =
                            SessionAuthenticationToken(accessor.sessionId, userDetails, token, userDetails.authorities)
                        accessor.user = authentication
                    } else {
                        accessor.setAnonymous()
                    }
                } else {
                    accessor.setAnonymous()
                }
            }

        }
        return message
    }

    fun StompHeaderAccessor.setAnonymous() {
        val r = Role()
        r.name = RoleConst.ROLE_ANONYMOUS
        // 匿名用户
        user = AnonymousAuthenticationToken(
            sessionId,
            sessionId,
            listOf(SimpleGrantedAuthority(r))
        )
    }

}